/*
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * This file is part of Harmattan SmashMiner OpenGL game application.
 * 
 * Harmattan SmashMiner OpenGL Example Application version 1.0.0
 *  
 * Latest update: 15.4.2011
 *
 * The Harmattan SmashMiner OpenGL example application demonstrates how to use 
 * the OpenGL ES in Harmattan devices.
 *
 * This example is provided as a starting point for 3rd party
 * developers (you) to ease the implementation of OpenGL based 
 * games. Ideas, parts of code or methodologies that this
 * example application uses can be freely used without restrictions.
 *
 * See file README.txt how to build and compile this example application
 * in the Harmattan SDK environment. 
 * 
 * See file INSTALL.txt to find out what is required to run this
 * application and how to install the application to the device or
 * alternatively in QEMU-emulator.
 *
 * The LICENSE.txt file explains the license this software is provided
 * with. A copy of the same license is included in this source file.
 * 
 */

/*
 * Copyright (C) 2011 by Nokia Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "soundenginepulseaudio.h"

#ifdef SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_PULSE_AUDIO

#include <QDebug>
#include <QBuffer>
#include "pulseaudiooutput.h"

//
// CALLBACKS for PulseAudio server
//
//
static void contextStateCallbackInit(pa_context *context, void *userdata)
{
    Q_UNUSED(context);
    SoundEnginePulseAudio *soundEngine = reinterpret_cast<SoundEnginePulseAudio*>(userdata);
    pa_threaded_mainloop_signal(soundEngine->mainloop(), 0);

#ifdef DEBUG_PULSE_AUDIO

    qDebug() << "Pulse Audio state = " << pa_context_get_state(context);

#endif

}

static void contextStateCallback(pa_context *context, void *userdata)
{
    Q_UNUSED(userdata);
    Q_UNUSED(context);

#ifdef DEBUG_PULSE_AUDIO

    qDebug() << "Pulse Audio state = "<< pa_context_get_state(context);

#endif

}


static void serverInfoCallback(pa_context *context, const pa_server_info *info, void *userdata)
{
    if (!info) {
        qWarning() << QString("Failed to get server information: %s").arg(pa_strerror(pa_context_errno(context)));
        return;
    }

#ifdef DEBUG_PULSE_AUDIO

    char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];

    pa_sample_spec_snprint(ss, sizeof(ss), &info->sample_spec);
    pa_channel_map_snprint(cm, sizeof(cm), &info->channel_map);

    qDebug() << QString("User name: %1\n"
             "Host Name: %2\n"
             "Server Name: %3\n"
             "Server Version: %4\n"
             "Default Sample Specification: %5\n"
             "Default Channel Map: %6\n"
             "Default Sink: %7\n"
             "Default Source: %8\n").arg(
           info->user_name,
           info->host_name,
           info->server_name,
           info->server_version,
           ss,
           cm,
           info->default_sink_name,
           info->default_source_name);

#endif

    SoundEnginePulseAudio *soundEngine = static_cast<SoundEnginePulseAudio*>(userdata);
    soundEngine->defaultSink = info->default_sink_name;

    pa_threaded_mainloop_signal(soundEngine->mainloop(), 0);
}


static void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int isLast, void *userdata)
{
    SoundEnginePulseAudio *soundEngine = static_cast<SoundEnginePulseAudio*>(userdata);
    QMap<pa_sink_state, QString> stateMap;
    stateMap[PA_SINK_INVALID_STATE] = "n/a";
    stateMap[PA_SINK_RUNNING] = "RUNNING";
    stateMap[PA_SINK_IDLE] = "IDLE";
    stateMap[PA_SINK_SUSPENDED] = "SUSPENDED";

    if (isLast < 0) {
        qWarning() << QString("Failed to get sink information: %s").arg(pa_strerror(pa_context_errno(context)));
        return;
    }

    if (isLast) {
        pa_threaded_mainloop_signal(soundEngine->mainloop(), 0);
        return;
    }

    if (!info) {
        qWarning() << "No Pulse Audio server information";
        return;
    }

#ifdef DEBUG_PULSE_AUDIO

    qDebug() << QString("Sink #%1\n"
             "\tState: %2\n"
             "\tName: %3\n"
             "\tDescription: %4\n"
            ).arg(QString::number(info->index),
                  stateMap.value(info->state),
                  info->name,
                  info->description);

#endif

    QAudioFormat format = soundEngine->sampleSpecToAudioFormat(info->sample_spec);
    soundEngine->preferredFormats.insert(info->name, format);
    soundEngine->pulseSinks.append(info->name);
}

/*!
    \class SoundEnginePulseAudio
    Pulse Audio sound server client to play sound effects.
    Sound server client is using Pulse Audio Asynchronous C API.
    Creates also Pulse Audio output device for sound effect playback.

    Singleton design pattern is used when implementing SoundEnginePulseAudio to ensure that only one instance exists.

    Pulse Audio sound server is preferred way to play game sound effects because of low latency.

    \sa SoundEngine PulseAudioOutput
*/

// Instance
SoundEnginePulseAudio* SoundEnginePulseAudio::soundEngine = NULL;

//
// SoundEnginePulseAudio implementation
//
SoundEnginePulseAudio::SoundEnginePulseAudio(QObject *parent) : QObject(parent)
{
    bool keepGoing = true;
    bool ok = true;

    pulseMainLoop = pa_threaded_mainloop_new();

    if (pulseMainLoop == 0) {
        qWarning("Unable to create pulseaudio mainloop");
        return;
    }

    if (pa_threaded_mainloop_start(pulseMainLoop) != 0) {
        qWarning("Unable to start pulseaudio mainloop");
        pa_threaded_mainloop_free(pulseMainLoop);
        return;
    }

    pulseMainLoopApi = pa_threaded_mainloop_get_api(pulseMainLoop);

    pa_threaded_mainloop_lock(pulseMainLoop);

    pulseContext = pa_context_new(pulseMainLoopApi, 
        QString(QLatin1String("SmashMinerContext:%1")).arg(::getpid()).toAscii().constData());

    pa_context_set_state_callback(pulseContext, contextStateCallbackInit, this);

    if (!pulseContext) {
        qWarning("Unable to create new pulseaudio context");
        pa_threaded_mainloop_free(pulseMainLoop);
        return;
    }

    if (pa_context_connect(pulseContext, NULL, (pa_context_flags_t)0, NULL) < 0) {
        qWarning("Unable to create a connection to the pulseaudio context");
        pa_context_unref(pulseContext);
        pa_threaded_mainloop_free(pulseMainLoop);
        return;
    }

    pa_threaded_mainloop_wait(pulseMainLoop);

    while (keepGoing) {

        switch (pa_context_get_state(pulseContext)) {
            case PA_CONTEXT_CONNECTING:
            case PA_CONTEXT_AUTHORIZING:
            case PA_CONTEXT_SETTING_NAME:
                break;

            case PA_CONTEXT_READY:

#ifdef DEBUG_PULSE_AUDIO

                qDebug("Connection established.");

#endif

                keepGoing = false;
                break;

            case PA_CONTEXT_TERMINATED:
                qCritical("Context terminated.");
                keepGoing = false;
                ok = false;
                break;

            case PA_CONTEXT_FAILED:
            default:
                qCritical() << QString("Connection failure: %1").arg(pa_strerror(pa_context_errno(pulseContext)));
                keepGoing = false;
                ok = false;
        }

        if (keepGoing)
            pa_threaded_mainloop_wait(pulseMainLoop);

    } // end while (keepGoing)

    if (ok) {
        pa_context_set_state_callback(pulseContext, contextStateCallback, this);

    } else if (pulseContext) {
        pa_context_unref(pulseContext);
        pulseContext = 0;
        pa_threaded_mainloop_free(pulseMainLoop);
        pulseAudioOutput = NULL;
        qWarning() << "Error when creating Pulse Audio context. No sound output!";
        return;
    }

    pa_threaded_mainloop_unlock(pulseMainLoop);

    serverInfo();
    sinks();
    pulseAudioOutput = new PulseAudioOutput(defaultSink, this);
}

/*!
  Gets the instance from Pulse Audio sound server client.
*/
SoundEnginePulseAudio *SoundEnginePulseAudio::instance()
{
    if (soundEngine == NULL) {
        soundEngine = new SoundEnginePulseAudio();
    }
    return soundEngine;
}

/*!
  Sets the volume level.
*/
void SoundEnginePulseAudio::setVolumeLevel(int volumeLevel)
{
    if (pulseAudioOutput)
        pulseAudioOutput->setVolumeLevel(volumeLevel);
}

/*!
  Plays the sound affect given at the parameter \a soundEffect.
  \sa ::SoundEffectType.
*/
void SoundEnginePulseAudio::play(SoundEffectType soundEffect)
{

#ifdef DEBUG_PULSE_AUDIO

    qDebug() << "play() = " << soundEffect;

#endif

    if (pulseAudioOutput)
        pulseAudioOutput->start(soundEffect);
}

/*!
    Adds sound data buffer for the sound effect.
    Type of sound effect is given at the \a effectType parameter.
    Sound data is given at the \a buffer parameter.
    \sa ::SoundEffectType.
*/
void SoundEnginePulseAudio::addEffectBuffer(SoundEffectType effectType, QByteArray *buffer)
{
    if (pulseAudioOutput) {
        QBuffer *soundBuffer = new QBuffer(buffer);
        pulseAudioOutput->addSoundBuffer(effectType, soundBuffer);
    }
}

/*!
  Sets the audio format used at playback. Format is given at the parameter \a format.
*/
void SoundEnginePulseAudio::setAudioFormat(const QAudioFormat &format)
{
    audioFormat = format;
    if (pulseAudioOutput)
        pulseAudioOutput->setFormat(format);
}

/*!
  Gets the server info form Pulse Audio server via serverInfoCallback().
*/
void SoundEnginePulseAudio::serverInfo()
{
    pa_operation *operation;

    pa_threaded_mainloop_lock(pulseMainLoop);

    operation = pa_context_get_server_info(pulseContext, serverInfoCallback, this);

    while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
        pa_threaded_mainloop_wait(pulseMainLoop);

    pa_operation_unref(operation);

    pa_threaded_mainloop_unlock(pulseMainLoop);
}

/*!
  Gets the sinks info form Pulse Audio server via sinkInfoCallback().
*/
void SoundEnginePulseAudio::sinks()
{
    pa_operation *operation;

    pa_threaded_mainloop_lock(pulseMainLoop);

    operation = pa_context_get_sink_info_list(pulseContext, sinkInfoCallback, this);

    while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
        pa_threaded_mainloop_wait(pulseMainLoop);

    pa_operation_unref(operation);

    pa_threaded_mainloop_unlock(pulseMainLoop);

    // Swap the default sink to index 0
    pulseSinks.removeOne(defaultSink);
    pulseSinks.prepend(defaultSink);
}

//
// HELPERS
//
/*!
  Converts the QAudioFormat to pa_sample_spec.
*/
pa_sample_spec SoundEnginePulseAudio::audioFormatToSampleSpec(const QAudioFormat &format)
{
    pa_sample_spec  spec;

    spec.rate = format.frequency();
    spec.channels = format.channels();

    if (format.sampleSize() == 8) {
        spec.format = PA_SAMPLE_U8;
    } else if (format.sampleSize() == 16) {
        switch (format.byteOrder()) {
        case QAudioFormat::BigEndian: spec.format = PA_SAMPLE_S16BE; break;
        case QAudioFormat::LittleEndian: spec.format = PA_SAMPLE_S16LE; break;
        }
    } else if (format.sampleSize() == 24) {
        switch (format.byteOrder()) {
        case QAudioFormat::BigEndian: spec.format = PA_SAMPLE_S24BE; break;
        case QAudioFormat::LittleEndian: spec.format = PA_SAMPLE_S24LE; break;
        }
    } else if (format.sampleSize() == 32) {
        switch (format.byteOrder()) {
        case QAudioFormat::BigEndian:
            format.sampleType() == QAudioFormat::Float ? spec.format = PA_SAMPLE_FLOAT32BE : spec.format = PA_SAMPLE_S32BE;
            break;
        case QAudioFormat::LittleEndian:
            format.sampleType() == QAudioFormat::Float ? spec.format = PA_SAMPLE_FLOAT32LE : spec.format = PA_SAMPLE_S32LE;
            break;
        }
    } else {
        spec.format = PA_SAMPLE_INVALID;
    }

    return spec;
}

/*!
    Converts pa_sample_spec to QAudioFormat.
*/
QAudioFormat SoundEnginePulseAudio::sampleSpecToAudioFormat(pa_sample_spec spec)
{
    QAudioFormat format;
    format.setFrequency(spec.rate);
    format.setChannelCount(spec.channels);
    format.setCodec("audio/pcm");

    switch (spec.format) {
        case PA_SAMPLE_U8:
            format.setByteOrder(QAudioFormat::LittleEndian);
            format.setSampleType(QAudioFormat::UnSignedInt);
            format.setSampleSize(8);
        break;
        case PA_SAMPLE_S16LE:
            format.setByteOrder(QAudioFormat::LittleEndian);
            format.setSampleType(QAudioFormat::SignedInt);
            format.setSampleSize(16);
        break;
        case PA_SAMPLE_S16BE:
            format.setByteOrder(QAudioFormat::BigEndian);
            format.setSampleType(QAudioFormat::SignedInt);
            format.setSampleSize(16);
        break;
        case PA_SAMPLE_FLOAT32LE:
            format.setByteOrder(QAudioFormat::LittleEndian);
            format.setSampleType(QAudioFormat::Float);
            format.setSampleSize(32);
        break;
        case PA_SAMPLE_FLOAT32BE:
            format.setByteOrder(QAudioFormat::BigEndian);
            format.setSampleType(QAudioFormat::Float);
            format.setSampleSize(32);
        break;
        case PA_SAMPLE_S32LE:
            format.setByteOrder(QAudioFormat::LittleEndian);
            format.setSampleType(QAudioFormat::SignedInt);
            format.setSampleSize(32);
        break;
        case PA_SAMPLE_S32BE:
            format.setByteOrder(QAudioFormat::BigEndian);
            format.setSampleType(QAudioFormat::SignedInt);
            format.setSampleSize(32);
        break;
        case PA_SAMPLE_S24LE:
            format.setByteOrder(QAudioFormat::LittleEndian);
            format.setSampleType(QAudioFormat::SignedInt);
            format.setSampleSize(24);
        break;
        case PA_SAMPLE_S24BE:
            format.setByteOrder(QAudioFormat::BigEndian);
            format.setSampleType(QAudioFormat::SignedInt);
            format.setSampleSize(24);
        break;
        case PA_SAMPLE_S24_32LE:
            format.setByteOrder(QAudioFormat::LittleEndian);
            format.setSampleType(QAudioFormat::SignedInt);
            format.setSampleSize(24);
        break;
        case PA_SAMPLE_S24_32BE:
            format.setByteOrder(QAudioFormat::BigEndian);
            format.setSampleType(QAudioFormat::SignedInt);
            format.setSampleSize(24);
        break;
        case PA_SAMPLE_MAX:
        case PA_SAMPLE_INVALID:
        default:
            format.setByteOrder(QAudioFormat::LittleEndian);
            format.setSampleType(QAudioFormat::Unknown);
            format.setSampleSize(0);
    }

    return format;
}

#ifdef DEBUG_PULSE_AUDIO

QString SoundEnginePulseAudio::sampleFormatToQString(pa_sample_format format)
{
    switch (format) {
        case PA_SAMPLE_U8:          return "Unsigned 8 Bit PCM.";
        case PA_SAMPLE_ALAW:        return "8 Bit a-Law ";
        case PA_SAMPLE_ULAW:        return "8 Bit mu-Law";
        case PA_SAMPLE_S16LE:       return "Signed 16 Bit PCM, little endian (PC).";
        case PA_SAMPLE_S16BE:       return "Signed 16 Bit PCM, big endian.";
        case PA_SAMPLE_FLOAT32LE:   return "32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0";
        case PA_SAMPLE_FLOAT32BE:   return "32 Bit IEEE floating point, big endian, range -1.0 to 1.0";
        case PA_SAMPLE_S32LE:       return "Signed 32 Bit PCM, little endian (PC).";
        case PA_SAMPLE_S32BE:       return "Signed 32 Bit PCM, big endian.";
        case PA_SAMPLE_S24LE:       return "Signed 24 Bit PCM packed, little endian (PC).";
        case PA_SAMPLE_S24BE:       return "Signed 24 Bit PCM packed, big endian.";
        case PA_SAMPLE_S24_32LE:    return "Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC).";
        case PA_SAMPLE_S24_32BE:    return "Signed 24 Bit PCM in LSB of 32 Bit words, big endian.";
        case PA_SAMPLE_MAX:         return "Upper limit of valid sample types.";
        case PA_SAMPLE_INVALID:     return "Invalid sample format";
    }

    return QString("Invalid value: %0").arg(format);
}

#endif // DEBUG_PULSE_AUDIO

#endif  // SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_PULSE_AUDIO

